/**
 * HTML解析器
 * @param options
 * @constructor
 */
function JKHTMLParser(options) {
    options = options || {};
    JKHTMLParser.autoSelfCloseTags = ['br', 'meta', 'link', 'hr', 'img', 'input'];
    JKHTMLParser.noElmTags = ['style', 'script', 'xmp'];
    JKHTMLParser.tagRule = /^[\w-]+\s*/;

    let _autoSelfCloseTags = options.autoSelfCloseTags || JKHTMLParser.autoSelfCloseTags;
    let _noElmTags = options.noElmTags || JKHTMLParser.noElmTags;
    let _tagRule = options.tagRule || JKHTMLParser.tagRule;

    let TYPE = {
        COMMENT: 'comment',
        ELEMENT: 'elm',
        TEXT: 'text',
        ROOT: 'root',
    }

    function trim(s) {
        return s.replace(/^\s+|\s+$/g, '')
    }

    function JKHTMLElement(type, tag, addition) {
        type = type || TYPE.ELEMENT;
        tag = (tag || '').toLowerCase();
        addition = addition || {};

        this.type = type.toLowerCase();
        this.tag = tag.toLowerCase();
        this.originTag = tag;
        this.attributes = addition.attributes || {};
        this.value = addition.value || '';
        this.children = addition.children || [];
        this.selfclose = !!addition.selfclose;
        this.parent = null;

        this.appendChild = function(child) {
            child.parent = this;
            this.children.push(child);
        };

        /**
         * 或者儿子元素(非文本元素)
         * @returns {[]|*[]}
         */
        this.getChildElements = function () {
            if(this.type != TYPE.ELEMENT) {
                return  [];
            }

            let ary = [];

            for(let i in this.children) {
                if(this.children[i].type == TYPE.ELEMENT) {
                    ary.push(this.children[i]);
                }
            }

            return ary;
        };

        /**
         * 获取所有的子元素(非文本元素)
         * @returns {[]|*[]}
         */
        this.getAllChildElements = function () {
            if(this.type != TYPE.ELEMENT) {
                return  [];
            }

            let ary = this.getChildElements();

            for(let i in ary) {
                ary = ary.concat(ary[i].getAllChildElements());
            }

            return ary;
        };

        this.getAttribute = function(name) {
            let attr = this.attributes[name.toLowerCase()];
            return attr ? attr.value : undefined;
        };

        this.setAttribute = function(name, value) {
            if(typeof value == 'undefined') {
                delete this.attributes[name.toLowerCase()];
                return;
            }

            this.attributes[name.toLowerCase()] = {
                name: name.toLowerCase(),
                originName: name,
                value
            }
        };

        this.removeAttribute = function(name) {
            this.setAttribute(name, undefined);
        };

        this.searchChildren = function(options) {
            return this.searchChild(options, true);
        };

        this.searchDirectChild = function (options) {
            options = options || {};
            options.directChild = true;
            return this.searchChild(options);
        };

        this.searchDirectChildren = function (options) {
            options = options || {};
            options.directChild = true;
            return this.searchChild(options, true);
        };

        /**
         * 搜索元素
         * @param options 支持三种方式:
         *  tags: 一个数组, 包含多个允许的标签名
         *  attributes: 包含指定属性值的元素
         *  classes: 包含指定class样式的元素
         * @param all
         * @returns {null|[]|*}
         */
        this.searchChild = function(options, all) {
            let tags = (options.tags || []).map(function(r){ return r.toLowerCase(); }),
                attributes = options.attributes || {},
                classes = options.classes || []
            ;

            let directChild = !!options.directChild;

            let children;

            if(directChild) {
                children = this.getChildElements();
            } else {
                children = this.getAllChildElements();
            }

            let result = [];

            for(let i=0; i<children.length; i++) {
                let child = children[i];

                if(tags.length > 0) {
                    if(tags.indexOf(child.tag) == -1) {
                        continue;
                    }
                }

                let attrNomatch = false;

                for(let attrname in attributes) {
                    attrname = attrname.toLowerCase();

                    if(attributes[attrname] === true) {
                        if(typeof  child.attributes[attrname] == 'undefined') {
                            attrNomatch = true;
                            break;
                        }
                    } else if(typeof child.attributes[attrname] == 'undefined'
                        || child.attributes[attrname].value != String(attributes[attrname])) {
                        attrNomatch = true;
                        break;
                    }
                }

                if(attrNomatch) {
                    continue;
                }

                let classesNomatch = false;

                if(classes.length > 0) {
                    if(typeof child.attributes['class'] == 'undefined') {
                        continue;
                    }

                    let currentClasses = (child.attributes['class'].value || '').split(' ').map(function(r) {return trim(r);});

                    for(let classIndex in classes) {
                        if(currentClasses.indexOf(classes[classIndex]) == -1) {
                            classesNomatch = true;
                            break;
                        }
                    }
                }

                if(classesNomatch) {
                    continue;
                }

                if(all) {
                    result.push(child);
                } else {
                    return child;
                }
            }

            if(all) {
                return result;
            } else {
                return null;
            }
        };

        this.getInnerHTML = function(standard) {
            return this.toHTML(standard, true);
        }

        this.toHTML = function(standard, inner) {
            if(this.type == TYPE.COMMENT) {
                return `<!--${this.value}-->`;
            }

            if(this.type == TYPE.TEXT) {
                return this.value;
            }

            let html = '';
            let attrary = [];
            let tag = standard ? this.tag : this.originTag;

            for(let i in this.attributes) {
                let attr = this.attributes[i];
                let name = standard ? attr.name : attr.originName;
                let val = attr.value;
                attrary.push(name + (val === null ? '' : '="'+ String(val).replace(/"/g, '&quot;') +'"'));
            }

            let attrstr = attrary.join(' ');
            attrstr = attrstr == '' ? '' : ' ' + attrstr;

            if(tag && !inner) {
                html += `<${tag}` + attrstr + (this.selfclose ? '/' : '') + '>';
            }

            for(let child of this.children) {
                html += child.toHTML(standard);
            }

            if(!this.selfclose && _autoSelfCloseTags.indexOf(this.tag)==-1 && tag && !inner) {
                html += `</${tag}>`;
            }

            return html;
        };

        this.toText = function (dotrim) {
            if(this.type == TYPE.TEXT) {
                return this.value;
            }

            let text = '';

            for(let child of this.children) {
                text += child.toText();
            }

            return dotrim ? trim(text) : text;
        };
    }

    JKHTMLElement.TYPE = TYPE;
    JKHTMLParser.Element = JKHTMLElement;

    function ParseTrace(elm) {
        this.trace = [];

        this.push = function(elm) {
            this.trace.push(elm);
        }

        this.pop = function() {
            return this.trace.pop();
        };

        this.last = function () {
            return this.trace[this.trace.length - 1];
        };

        if(elm) {
            this.push(elm);
        }
    }

    /**
     * 解析html
     */
    this.parse = function(str) {
        let root = new JKHTMLElement(TYPE.ROOT);
        let trace = new ParseTrace(root);
        let lastpos = 0,
            pos=0,
            needclose=false
        ;

        /**
         * 添加文本节点
         * @private
         */
        function _addText() {
            if(lastpos != pos) {
                let text;

                if(pos == -1) {
                    text = str.substring(lastpos);
                } else {
                    text = str.substring(lastpos, pos);
                }

                if(text) {
                    trace.last().appendChild(
                        new JKHTMLElement(TYPE.TEXT, null, {value: text})
                    );
                }

                lastpos = pos;
            }
        }

        /**
         * 解析注释
         * @returns {boolean}
         * @private
         */
        function _parseComment() {
            if(str.substr(pos+1, 3) != '!--') {
                return false;
            }

            _addText();

            let current = trace.last();
            let commentEnd = str.indexOf('-->', pos+4);
            let comment = "";

            if(commentEnd == -1) {
                comment = str.substr(pos+4);
                current.appendChild(
                    new JKHTMLElement(TYPE.COMMENT, null, {value:comment})
                );

                pos = str.length + 1;
                lastpos = str.length + 1;
                return true;
            }

            comment = str.substring(pos+4, commentEnd);
            current.appendChild(
                new JKHTMLElement(TYPE.COMMENT, null, {value:comment})
            );

            pos = commentEnd+3;
            lastpos = pos;
            return true;
        }

        /**
         * 解析结束标签
         * @returns {boolean}
         * @private
         */
        function _parseTagEnd() {
            if(str[pos+1] != '/') {
                return false;
            }

            let tagName = '';
            let targetPos = pos;

            for(let j=pos+2; j<str.length; j++) {
                if(/^[\s</\\]$/.test(str[j])) {
                    tagName = '';
                    targetPos = j;
                    break;
                }

                if(str[j] == '>') {
                    targetPos = j;
                    break;
                }

                tagName += str[j];
            }

            if(tagName == '') {
                pos = targetPos+1;
                return false;
            }

            if(tagName.toLowerCase() != trace.last().tag) {
                pos = targetPos+1;
                return false;
            }

            _addText();
            trace.pop();
            pos = targetPos+1;
            lastpos = pos;
            return true;
        }

        /**
         * 解析标签属性
         * @param str
         * @returns {boolean|[]}
         * @private
         */
        function _parseTagAttr(str) {
            let attrs = {};
            str = str.replace(/^=+/, '');

            let ineq = false, inspace=false, quote='';
            let name='', val='';

            let addAttr = () => {
                name = trim(name);
                val = trim(val);

                if(val == "" && !ineq) {
                    val = null;
                }

                if(name != "") {
                    attrs[name.toLowerCase()] = {
                        name:name.toLowerCase(),
                        originName:name,
                        value:val
                    }
                }

                name = "";
                val = "";
                ineq = false;
                quote = '';
                inspace = false;
            };

            for(let i=0; i<str.length; i++) {
                let char = str[i];

                // 单个属性结束
                if(/\s/.test(char)) {
                    if(!ineq && !quote) {
                        inspace = true;
                        continue;
                    }

                    if(ineq && !quote && trim(val) != '') {
                        addAttr()
                        continue;
                    }
                }

                if(char == '=') {
                    if(!quote && !ineq && trim(name) != '') {
                        ineq = true;
                        inspace = false;
                        continue;
                    }
                }

                if(char == '"' || char == "'") {
                    if(ineq && !quote && trim(val) == '') {
                        quote = char;
                        continue;
                    } else if(ineq && quote == char) {
                        addAttr();
                        continue;
                    }
                }

                if(inspace) {
                    addAttr();
                }

                if(ineq) {
                    val += char;
                } else {
                    name += char;
                }
            }

            if(quote) {
                return false;
            }

            if(trim(name)) {
                addAttr()
            }

            return attrs;
        }

        /**
         * 解析标签
         * @returns {boolean}
         * @private
         */
        function _parseTag() {
            let current = trace.last();

            let rightPos = str.indexOf('>', pos+1);

            if(rightPos == -1) {
                pos++;
                return false;
            }

            // 标签属性字符串
            let tagStr = str.substring(pos+1, rightPos);
            let tag = tagStr;

            if(!_tagRule.test(tagStr)) {
                pos++;
                return false;
            }

            _addText();

            let ret = tagStr.match(/[\s\/]/);

            if(ret) {
                tag = tagStr.substr(0, ret.index);
                tagStr = tagStr.substr(ret.index);
                pos += ret.index;
            } else {
                pos = rightPos+1;
                tagStr = "";
            }

            let selfclose, attrs={};

            if(tagStr != "") {
                // 当前找到的'>'并非标签行结束, 找到下一个'>'
                while(true) {
                    selfclose = false;

                    if(tagStr[tagStr.length - 1] == '/') {
                        selfclose = true;
                        tagStr = tagStr.substr(0, tagStr.length-1);
                    }

                    attrs = _parseTagAttr(tagStr);

                    if(false === attrs) {
                        rightPos = str.indexOf('>', rightPos+1);

                        if(rightPos == -1) {
                            pos = str.length + 1;
                            return false;
                        }

                        tagStr = str.substring(pos+1, rightPos);
                    } else {
                        break;
                    }
                }
            }

            if(!tag) {
                return false;
            }

            let elm = new JKHTMLElement(TYPE.ELEMENT, tag, {attributes: attrs, selfclose:selfclose});
            current.appendChild(elm);

            if(!selfclose && _autoSelfCloseTags.indexOf(tag.toLowerCase()) !== -1) {
                selfclose = true;
            }

            if(!selfclose) {
                trace.push(elm);

                //无子元素的标签
                if(_noElmTags.indexOf(elm.tag) != -1) {
                    needclose = true;
                }
            }

            pos = rightPos+1;
            lastpos = pos;
            return true;
        }

        str = trim(str);

        while(true) {
            if(needclose) {
                //需要找结束标签, 以加快解析速度
                pos = str.indexOf('</', pos);
            } else {
                pos = str.indexOf('<', pos);
            }

            if(pos == -1) {
                break;
            }

            if(needclose) {
                //找到结束标签
                if(_parseTagEnd()) {
                    needclose = false;
                }

                continue;
            }

            //注释
            if(_parseComment()) {
                continue;
            }

            // 结束标签
            if(_parseTagEnd()) {
                continue;
            }

            //标签
            _parseTag();
        }

        _addText();

        if(root.children.length == 1) {
            return root.children.pop();
        } else {
            return root.children;
        }
    };
}

module.exports = JKHTMLParser;
